package dbexp.test;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;

public class SnoopOracle extends Thread {
	static char hex_map[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
			'A', 'B', 'C', 'D', 'E', 'F' };
	static boolean display_time = false;
	static int lineSize = 20;
	static int BUFF_SIZE = 32768;
	String direction;
	Socket from_socket;
	Socket to_socket;
	PrintWriter trace_writer;
	String server_name;
	int new_server_port;

	public SnoopOracle(String s, Socket socket, Socket socket1,
			PrintWriter printwriter) {
		from_socket = socket;
		to_socket = socket1;
		direction = s;
		trace_writer = printwriter;
		new_server_port = 0;
	}

	public SnoopOracle(String as[]) {
		if (as.length < 2) {
			System.out.println("Syntax: java Snoop <server> <port> [options]");
			System.out.println("Options: ");
			System.out
					.println("  -snoopPort <local port #>: Local port number to expose");
			System.out
					.println("  -t:                        Display timestamp in millisecond");
			System.out
					.println("  -lineSize <val>:           Number of bytes to appear on each snoop file line (default=20)");
			return;
		}
		String s = null;
		int i = 0;
		s = as[0];
		i = Integer.parseInt(as[1]);
		int j = i;
		for (int k = 2; k < as.length; k++)
			if (as[k].equals("-t"))
				display_time = true;
			else if (as[k].equals("-lineSize"))
				lineSize = Integer.parseInt(as[++k]);
			else if (as[k].equals("-snoopPort"))
				j = Integer.parseInt(as[++k]);

		String s1 = null;
		try {
			InetAddress.getByName(s);
			ServerSocket serversocket = new ServerSocket(j);
			System.out.println("Connecting to " + s + ":" + i);
			System.out.println("Listening to local port " + j + " ...");
			do {
				Socket socket = serversocket.accept();
				System.out.println("\nClient connection established.");
				Socket socket1 = new Socket(s, i);
				PrintWriter printwriter = getTraceFile(s1);
				trace_writer = printwriter;
				printwriter.println("Server = " + s + ":" + i);
				printwriter.println();
				printwriter.println("HEX\t\t\t\tASCII");
				printwriter.println();
				byte abyte0[] = receive(socket);
				abyte0 = updateHost(abyte0, s);
				if (j != i)
					abyte0 = updatePort(abyte0, i);
				log("Send: ", abyte0);
				send(socket1, abyte0);
				byte abyte1[] = receive(socket1);
				log("Read: ", abyte1);
				while (abyte1[4] == 5) {
					int l = getPort(abyte1);
					String s2 = getHost(abyte1);
					log("Redirected to " + s2 + ":" + l + "\n");
					socket1.close();
					socket1 = new Socket(s2, l);
					abyte0 = updateHost(abyte0, s2);
					abyte0 = updatePort(abyte0, l);
					send(socket1, abyte0);
					log("Send: ", abyte0);
					abyte1 = receive(socket1);
					log("Read: ", abyte1);
				}
				send(socket, abyte1);
				(new SnoopOracle("Send", socket, socket1, printwriter)).start();
				(new SnoopOracle("Read", socket1, socket, printwriter)).start();
			} while (true);
		} catch (UnknownHostException _ex) {
			System.out.println("IP address for " + s + " can't be found.");
		} catch (Exception exception) {
			System.out.println("  Error: " + exception.getMessage());
		}
	}

	char byteToASCII(byte byte0) {
		if (byte0 > 31 && byte0 < 127)
			return (char) byte0;
		else
			return '.';
	}

	char[] byteToHex(byte byte0) {
		char ac[] = { hex_map[byte0 >> 4 & 0xf], hex_map[byte0 & 0xf] };
		return ac;
	}

	static String getHost(byte abyte0[]) {
		String s = new String(abyte0);
		int i = s.indexOf("HOST=");
		int j = s.indexOf(")", i);
		return s.substring(i + 5, j);
	}

	static int getPort(byte abyte0[]) {
		String s = new String(abyte0);
		int i = s.indexOf("PORT=");
		int j = s.indexOf(")", i);
		return Integer.parseInt(s.substring(i + 5, j));
	}

	static PrintWriter getTraceFile(String s) throws IOException {
		File file;
		if (s != null) {
			file = new File(s);
		} else {
			int i = 0;
			do {
				file = new File(Integer.toString(i) + ".out");
				if (!file.exists())
					break;
				i++;
			} while (true);
		}
		System.out.println("Creating snoop file " + file.getPath());
		return new PrintWriter(new FileWriter(file), true);
	}

	void log(String s) {
		trace_writer.println(s);
		trace_writer.flush();
	}

	void log(String s, byte abyte0[]) {
		direction = s;
		log(abyte0, abyte0.length);
	}

	synchronized void log(byte abyte0[], int i) {
		int j = 0;
		StringBuffer stringbuffer = new StringBuffer(64);
		if (display_time)
			trace_writer.print(System.currentTimeMillis() + " ");
		trace_writer.println(direction + ": " + i + " bytes");
		do {
			stringbuffer.setLength(0);
			for (int k = j; k < j + lineSize; k++) {
				if (k < i)
					stringbuffer.append(byteToHex(abyte0[k]));
				else
					stringbuffer.append("  ");
				stringbuffer.append(' ');
			}

			stringbuffer.append('\t');
			for (int l = j; l < j + lineSize; l++)
				if (l < i)
					stringbuffer.append(byteToASCII(abyte0[l]));
				else
					stringbuffer.append(' ');

			trace_writer.println(stringbuffer);
			j += lineSize;
		} while (j <= i);
		trace_writer.println("");
		trace_writer.flush();
	}

	synchronized void logToStandardOut(String s, byte abyte0[]) {
		int i = abyte0.length;
		int j = 0;
		StringBuffer stringbuffer = new StringBuffer(64);
		System.out.println(direction + ": " + i + " bytes");
		do {
			stringbuffer.setLength(0);
			for (int k = j; k < j + lineSize; k++) {
				if (k < i)
					stringbuffer.append(byteToHex(abyte0[k]));
				else
					stringbuffer.append("  ");
				stringbuffer.append(' ');
			}

			stringbuffer.append('\t');
			for (int l = j; l < j + lineSize; l++)
				if (l < i)
					stringbuffer.append(byteToASCII(abyte0[l]));
				else
					stringbuffer.append(' ');

			System.out.println(stringbuffer);
			j += lineSize;
		} while (j <= i);
		System.out.println("");
		System.out.flush();
	}

	public static void main(String args[]) {
		if(args.length==0){
			args = new String[]{"localhost", "15211"};
		}
		new SnoopOracle(args);
	}

	static byte[] receive(Socket socket) throws IOException {
		InputStream inputstream = socket.getInputStream();
		byte abyte0[] = new byte[BUFF_SIZE];
		int i = inputstream.read(abyte0);
		byte abyte1[] = new byte[i];
		System.arraycopy(abyte0, 0, abyte1, 0, i);
		return abyte1;
	}

	public void run() {
		try {
			byte abyte0[] = new byte[BUFF_SIZE];
			InputStream inputstream = from_socket.getInputStream();
			OutputStream outputstream = to_socket.getOutputStream();
			do {
				int i;
				try {
					i = inputstream.read(abyte0);
				} catch (SocketException _ex) {
					break;
				}
				if (i == -1) {
					String s = direction.equals("Send") ? "Client" : "Server";
					System.out.println(s + " connection closed.");
					break;
				}
				log(abyte0, i);
				outputstream.write(abyte0, 0, i);
			} while (true);
		} catch (IOException ioexception) {
			System.out.println("  Error: " + ioexception.getMessage());
		} finally {
			try {
				if (from_socket != null)
					from_socket.close();
				if (to_socket != null)
					to_socket.close();
			} catch (Exception _ex) {
			}
		}
	}

	static void send(Socket socket, byte abyte0[]) throws IOException {
		OutputStream outputstream = socket.getOutputStream();
		outputstream.write(abyte0, 0, abyte0.length);
	}

	static byte[] updateHost(byte abyte0[], String s) {
		String s1 = new String(abyte0);
		int i = s1.indexOf("HOST=");
		int j = s1.indexOf(")", i);
		s1 = s1.substring(0, i + 5) + s + s1.substring(j);
		byte abyte1[] = s1.getBytes();
		updateLength(abyte1, abyte0);
		return abyte1;
	}

	static void updateLength(byte abyte0[], byte abyte1[]) {
		int i = abyte0.length;
		int j = abyte1.length;
		abyte0[0] = (byte) (i >> 8 & 0xff);
		abyte0[1] = (byte) (i & 0xff);
		int k = abyte1[24] << 8 & 0xff | abyte1[25] & 0xff;
		int l = k + (i - j);
		abyte0[24] = (byte) (l >> 8 & 0xff);
		abyte0[25] = (byte) (l & 0xff);
	}

	static byte[] updatePort(byte abyte0[], int i) {
		String s = new String(abyte0);
		int j = s.indexOf("PORT=");
		int k = s.indexOf(")", j);
		s = s.substring(0, j + 5) + i + s.substring(k);
		byte abyte1[] = s.getBytes();
		updateLength(abyte1, abyte0);
		return abyte1;
	}
}
